package evs.location;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.Collection;

import javax.persistence.Entity;
import javax.persistence.Transient;

public class EntitySerializer
{
    private final EntityMap _entityMap;
    private final ArrayList<Object> _serializedObjects = new ArrayList<Object>(); 
    
    public EntitySerializer( EntityMap entityMap )
    {
        _entityMap = entityMap;
    }
    
    public EntityDom serialize( Object entity ) 
    {
        EntityDom dom = new EntityDom();
        
        Class<?> entityClass = entity.getClass();
        
        dom.name = _entityMap.getNameForClass( entityClass );
        
        boolean hasAlreadyBeenSerialized = _hasAlreadyBeenSerialized( entity );
        Field idField = _entityMap.getIdField( entityClass );
        
        _serializedObjects.add( entity );
        
        for ( Field field : EntityMap.getFields( entityClass ) )
        {
            if ( field.isAnnotationPresent( Transient.class ) || 
                 ( field.getModifiers() & Modifier.TRANSIENT ) != 0 )
                continue;
        
            if ( hasAlreadyBeenSerialized && !field.equals( idField ) )
                continue;
            
            field.setAccessible( true );
            
            try
            {
                if ( field.getType().isAnnotationPresent( Entity.class ) )
                {
                    EntityDom fieldDom = new EntityDom();
                    fieldDom.name = field.getName();
                    
                    fieldDom.addChild( serialize( field.get( entity ) ) );
                    dom.addChild( fieldDom );
                }
                else if ( Collection.class.isAssignableFrom( field.getType() ) )
                {
                    dom.addChild( serializeCollection( entity, field ) );
                }
                else
                {                    
                    EntityDom fieldDom = new EntityDom();
                    fieldDom.name = field.getName();
                    
                    Object value = field.get( entity );
                    
                    fieldDom.value = value != null ? Converter.convertToString( value ) : null;
                    
                    dom.addChild( fieldDom );
                }
            }
            catch ( IllegalArgumentException e )
            {
                e.printStackTrace();
            }
            catch ( IllegalAccessException e )
            {
                e.printStackTrace();
            }   
        }
        
        return dom;
    }


    private EntityDom serializeCollection( Object entity, Field field ) 
        throws IllegalArgumentException, IllegalAccessException
    {
        EntityDom collectionDom = new EntityDom();
        collectionDom.name = field.getName();
        collectionDom.isCollection = true;
        
        ParameterizedType pt = (ParameterizedType) field.getGenericType();
        Class<?> componentType = (Class<?>) pt.getActualTypeArguments()[0];
        
        Collection<?> collection = (Collection<?>) field.get( entity );
        
        if ( componentType.isAnnotationPresent( Entity.class ) )
        {
            for ( Object item : collection )
            {
                collectionDom.addChild( serialize( item ) );
            }
        }
        else
        {
            for ( Object item : collection )
            {
                EntityDom itemDom = new EntityDom();
                itemDom.name = componentType.getSimpleName();
                itemDom.value = Converter.convertToString( item );
            }
        }
        
        return collectionDom;
    }
    
    private boolean _hasAlreadyBeenSerialized( Object o )
    {
        for ( Object o2 : _serializedObjects )
            if ( o == o2 )
                return true;
        
        return false;
    }
}
